1 - Entendimento do problema

A nossa empresa e seus investidores decidiram montar um time de futebol na Europa com a mesma qualidade dos melhores times europeus da atualidade. Para isso, solicitaram uma análise da equipe de BI sobre quais jogadores poderiam adquirir, desde que tivessem um preço competitivo. Devemos contratar 11 jogadores titulares para as seguintes posições:

Posição Descrição Jogadores
GK Goleiro 1
Center-back Zagueiro central 2
Outside-back Zagueiro lateral 2
Center-mid Meio de campo 2
Outside-mid Lateral 2
Forward Atacante 2

As posições detalhadas podem ser encontradas na figura a seguir:

1.1 Fonte das informações

Como não é possível medir todos os jogadores do mundo a partir de agora, o novo time considerou aceitável usar a base de dados com 17994 jogadores (originários do jogo Fifa 18).

  • As informações básicas dos jogadores estão no arquivo Excel:
  • As habilidades dos jogadores foram catalogadas na base de dados:
    • Servidor (host): 35.225.23.89
    • Tipo de Banco de dados: PostgreSQL (pacote RPostgres)
    • Porta (port): 55432
    • Database (db): fiapbi
    • Usuário (user): biuser
    • Senha (password): biuser
    • Schema e Tabela: futebol.habilities
  • As características físicas dos jogadores estão no mesmo banco de dados, porém em outra tabela:
    • Schema e Tabela: futebol.body
  • Os dados financeiros dos jogadores estão no arquivo CSV encontrado em:

1.2 Dicionário de dados

  • Informações dos jogadores:
    ID, name, full_name, club, club_logo, special, league, flag, nationality, photo, Position.

  • Habilidades do jogador (numéricos):
    crossing, finishing, heading_accuracy, short_passing, volleys, dribbling, curve, free_kick_accuracy, long_passing, ball_control, acceleration, sprint_speed, agility, reactions, balance, shot_power, jumping, stamina, strength, long_shots, aggression, interceptions, positioning, vision, penalties, composure, marking, standing_tackle, sliding_tackle, gk_diving, gk_handling, gk_kicking, gk_positioning, gk_reflexes.

  • Características físicas:
    Numéricos: age, height_cm, weight_kg.
    Categóricos: birth_date, body_type.
    Booleano: real_face

  • Dados financeiros (numéricos):
    eur_value, eur_wage, eur_release_clause


2 - Preparativos para a análise (Setup)

Para iniciarmos a análise é preciso instalar e carregar alguns pacotes. Esta seção é considerada um cabeçalho e evolui conforme a análise acontece.

Obs.: Caso sua estação não possua algum dos pacotes, é necessário instalar antes!

library(DBI) #Interface para base de dados
library(RPostgres) #Conexão com base de dados
library(readr) #Leitura de arquivo CSV
library(openxlsx) # Leitura de arquivo Excel
library(dplyr) #Manipulação de dados
library(plotly) #Gráficos Interativos
library(randomForest) #Aprendizado estatístico Advanced Analytics
library(magrittr)

A seguir, serão criadas algumas funções auxiliares que serão utilizadas em diversos trechos de análise.

#O professor criou a função abaixo para dar o display de um gráfico do plotly sempre com o mesmo tamanho
display.graph <- function(mygraph, width = 800, height = 500, margin = 0){
  ret <- mygraph %>% 
    layout(autosize = F, width = width, height = height, margin = margin) %>%
    config(showLink = F)
  return ( ret )
}

3 - Obtenção de dados

A seguir temos exemplos de obtenção de dados de arquivo csv, arquivo Excel e banco de dados. Utilize seu conhecimento para obter as informações requeridas indicadas na seção 1:

  • Informações dos jogadores
  • Habilidades do jogador
  • Características físicas
  • Dados financeiros

3.1 Exemplos:

# Exemplo Excel
exemplo.cameras <- read.xlsx("https://storage.googleapis.com/ds-publico/cameras.baltimore.xlsx")

# Exemplo CSV
exemplo.copas <- read_csv(url("https://storage.googleapis.com/ds-publico/Copas.csv"))
# Exemplo DB
con <- dbConnect(Postgres(), host="35.225.23.89", port=55432,
                 db="fiapbi", user="biuser", password="biuser")

exemplo.starwars <- dbGetQuery(con, "SELECT * FROM public.starwars")
exemplo.school_persons <- dbGetQuery(con, "SELECT * FROM reports.school_persons")
dbDisconnect(con)
rm(con)

3.2 Obtenção dos dados

Insira na célula a seguir os passos para obtenção dos dados mencionados. Lembre-se de armazenar em variáveis com nomes apropriados.

## COLOQUE SUA RESPOSTA AQUI
players = read.xlsx("https://storage.googleapis.com/ds-publico/Fifa/jogadores.xlsx")

con = dbConnect(Postgres(), host="35.225.23.89", port=55432, db="fiapbi", user="biuser", password="biuser")

habilities = dbGetQuery(con, "SELECT * FROM futebol.habilities")
physical  = dbGetQuery(con, "SELECT * FROM futebol.body")

dbDisconnect(con)
rm(con)

finances = read_csv("https://storage.googleapis.com/ds-publico/Fifa/financial.csv")

3.3 Juntar todos os dados em fifa

Para facilitar todas as análises a seguir, crie uma tabela chamada fifa, com o “join” de todas as tabelas (use inner_join)

## COLOQUE SUA RESPOSTA AQUI
fifa = players %>% 
       inner_join(habilities,by="ID") %>% 
       inner_join(physical, by="ID") %>%
       inner_join(finances, by="ID")

3.4 Validações:

if(!'fifa' %in% ls()){
  print("Querido aluno, a tabela 'fifa' precisa existir!")
} else {
  print("Parece que está certo, vamos seguir!")
}

4 - Análise exploratória inicial

4.1 Conhecimento inicial dos dados

Todo processamento deve se iniciar de uma análise exploratória, ou seja, conhecer os dados que temos disponíveis.

Para isso, vamos usar alguns conceitos de estatística descritiva.

Os códigos a seguir exibem informações sobre uma tabela chamada mtcars, entre estes dados:
- Quantidade de linhas e colunas;
- Resumos estatísticos das colunas;
- Primeiros registros da tabela.

dim(mtcars)
summary(mtcars)
head(mtcars)

Use o exemplo acima e informe :

dim(fifa)
summary(fifa)
  • Qual a quantidade de jogadores no arquivo de jogadores: 17994

  • Qual o salário médio dos jogadores, em Euro: 11504

4.2 Ligas disponíveis e ligas de referência

O exemplo abaixo exibe quantas espécies diferentes temos na tabela exemplo.starwars

length( unique(exemplo.starwars$species) )

Baseado nisso, quantos Clubes e quantas ligas temos disponíveis:

length(unique(fifa$club))
length(unique(fifa$league))
  • Clubes: 648
  • Ligas: 42

Quantos jogadores temos em cada liga? Use o exemplo abaixo para responder.

exemplo.starwars %>%
  group_by(species) %>%
  summarise(Personagens = n()) %>%
  arrange(desc(Personagens))

Reposta:

fifa %>%
  group_by(league) %>%
  summarise(players = n()) %>%
  arrange(desc(players))

4.3 Liga de referência! (as melhores, segundo o prefessor) !

Nossa base de dados possui 647 times divididos em 41 ligas. As seguintes ligas serão selecionadas como as melhores ligas para análise dos jogadores:
- English Championship
- French Ligue 1
- German Bundesliga
- Spanish Primera División
- Italian Serie A (ok, sei que há controvérsias)

Para faciliar nosso trabalho futuro, armazenamos as ligas de referência selecionadas no vetor best_leagues:

best_leagues = c("English Championship", "French Ligue 1", "German Bundesliga", "Spanish Primera División", "Italian Serie A")
best_species = c("Droid", "Mirialan")

Agora exiba um gráfico nos informando quantos jogadores estão na liga de referência. Veja o exemplo abaixo que exibe quantos personages estão nas espécies de referência:

exemplo.starwars %>%
  group_by(species) %>%
  summarise(Personagens = n()) %>%
  arrange(desc(Personagens)) %>%
  mutate( Referencia = species %in% best_species) -> exemplo.agregacao
print(exemplo.agregacao)
## COLOQUE SUA RESPOSTA AQUI
n_players_by_league = fifa %>%
  group_by(league) %>%
  summarise(players = n()) %>%
  arrange(desc(players)) %>%
  mutate(reference = league %in% best_leagues) %T>%
  print()

4.4 Gráfico de barras

Exiba o gráfico de barras de todas as ligas, destacando as que são ligas de referência. Use o exemplo abaixo:

grafico <- exemplo.agregacao %>%
  filter(Referencia == F) %>%
  plot_ly(x = ~species, y = ~Personagens, color = ~Referencia, type = 'bar', name="Outras")
grafico <- grafico %>% add_bars(x = ~species, y = ~Personagens, color = ~Referencia, data = exemplo.agregacao[exemplo.agregacao$Referencia==T,], name="Best")


display.graph(grafico)

Minhas ligas, se tudo der certo, um gráfico como este deve ser exibido:

n_players_by_league %>%
  filter(reference == FALSE) %>%
  plot_ly(x = ~league, y = ~players, color = ~reference, type = 'bar', name = 'other') %>%
  add_bars(x = ~league, y = ~players, color = ~reference, data = filter(n_players_by_league, reference == TRUE), name = "best") %T>% display.graph()

5 - Análise Características físicas dos jogadores

Será que há diferenças físicas entre jogadores das ligas diferentes? Veja o exemplo a seguir:

par(mfrow=c(2,1))
exemplo.school_persons %>%
  filter(gender=="men") %>%
  .$earnings %>%
  hist(xlim=c(0, 300), main="Homens")
exemplo.school_persons %>%
  filter(gender=="women") %>%
  .$earnings %>%
  hist(xlim=c(0, 300), main="Mulheres")
par(mfrow=c(1,1))

Agora mostre a distribuição de altura dos ATACANTES ( Forward) das melhores ligas acima e das demais ligas abaixo.

par(mfrow=c(2,1))
forwards = filter(fifa, Position == "Forward")
forwards %>% filter(not(league %in% best_leagues)) %>% .$height_cm %>% hist(main = "other", xlim=c(150, 210), breaks = 20)
forwards %>% filter(league %in% best_leagues) %>% .$height_cm %>% hist(main = "best", xlim=c(150, 210), breaks= 20)
par(mfrow=c(1,1))

Exemplo de uma imagem esperada:

5.2 EXTRA! Exemplo de análises mais detalhadas.

if(!'fifa' %in% ls()){
  fifa <- read_csv("https://storage.googleapis.com/ds-publico/fifa%20game-3.csv",
                   locale = locale(encoding = "UTF-8"))
}
fifa <- fifa %>%
  mutate(Selected = league %in% best_leagues)

analise_posicao <- function(coluna, nome_col){
  fig <- fifa %>% plot_ly(type = 'violin') 
  
  fig <- fig %>%
    add_trace(
      x = ~Position[fifa$Selected == T],
      y = ~coluna[fifa$Selected == T],
      legendgroup = 'Top',
      scalegroup = 'Top',
      name = 'Top',
      side = 'negative',
      box = list( visible = T ),
      meanline = list( visible = T ),
      color = I("blue")
    ) 
  fig <- fig %>%
    add_trace(
      x = ~Position[fifa$Selected == F],
      y = ~coluna[fifa$Selected == F],
      legendgroup = 'Outras',
      scalegroup = 'Outras',
      name = 'Outras',
      side = 'positive',
      box = list( visible = T ),
      meanline = list( visible = T ),
      color = I("#449944")
    ) 
  
  fig <- fig %>%
    layout(
      xaxis = list( title = "Posição" ),
      yaxis = list( title = nome_col, zeroline = F ),
      violingap = 0,
      violingroupgap = 0,
      violinmode = 'overlay'
    )
  
  return(display.graph(fig))
}
analise_posicao(fifa$age, "Idade")
analise_posicao(fifa$height_cm, "Altura (cm)")
analise_posicao(fifa$weight_kg, "Peso (kg)")
analise_posicao(fifa$eur_value, "Valor de compra(EUR)")
analise_posicao(fifa$eur_wage, "Salário (EUR)")

E então podemos fazer as análises sobre essas características. Nas figuras podemos ver que os jogadores das ligas selecionadas são mais novos do que das demais ligas, independentes da posição enquanto que os jogadores das ligas não selecionadas tem um salário menor, independentemente da posição. O interessante dessas análises é que é possível ver a estatística descritiva de uma forma muito mais natural, incluindo médias, medianas, quartis, máximos e mínimos, além de uma curva de distribuição.

Como o objetivo é mostrar o poder da ferramenta, de forma não exaustiva, vou parar a análise exploratória por aqui. Percebam que com poucas linhas de código, conseguimos gerar gráficos interativos totalmente customizadas. Agora, deixarei as demais análises exploratórias para vocês e partir para a proposta de resolução do problema: Escolher nossos 11 jogadores, bons e baratos!

6 - Proposta de resolução do problema

Para resolver este problema, vamos tentar obter o preço justo do jogador (eur_value), baseado apenas em suas habilidades, identificadas no dicionário de dados.

Se estivéssemos olhando apenas uma habilidade, como dribbling, para atacantes, por exemplo, poderíamos traçar uma média, de acordo com essa habilidade, correto? Neste caso, poderíamos traçar um gráfico como este, presente na a seguir, em que cada ponto azul representa um atacante, e cada ponto laranja o salário médio dos atacantes que possuem a mesma nota de dribbling.

# CELULA APENAS PARA DEMONSTRACAO

#Obtém jogadores de referência e armazena em j_ref
j_ref <- fifa %>%
  filter(Position == "Forward") %>%
  filter(league %in% best_leagues)

# Obtem a media destes jogadores, POR dribbling e armazena em vlmedio
vlmedio <- j_ref %>%
  group_by(dribbling) %>%
  summarise(eur_value = mean(eur_value, na.rm = TRUE)) %>%
  mutate(descricao = paste("Média de dribbling ", dribbling) ) %>%
  select(dribbling, descricao, eur_value)
  
#Cria gráfico dos jogadores
fig <- plot_ly(j_ref, x = ~dribbling, y = ~eur_value,
               text=~full_name, type='scatter', mode='markers',
               name="Jogadores")

# Adiciona as médias
fig <- add_trace(fig, data=vlmedio,
                 x = ~dribbling, y = ~eur_value,
                 type='scatter', mode='markers', text= ~ paste("Media de", dribbling),
                 name="Médias" )

display.graph(fig)

Mas como fazer isso considerando mais do que uma habilidade? E se quisermos considerar todas as habilidades para definir o preço justo do jogador?

Sim, isso não apenas é possível como existem muitas técnicas possíveis para se chegar a este resultado. É preciso de um pouco de conhecimento estatístico ou de aprendizado de máquina, mas utilizar uma ferramenta como o R é o que diferencia grandes análises, podendo ultrapassar as análises dos simples relatórios explicativos.

Para este caso, vou usar um algoritmo de Breiman e Cutler chamado de Random Forest para Regressão. Trata-se de um modelo de aprendizado estatístico que avalia as diversas características (habilidades do jogador) e estabelece qual seria a resposta do modelo (valor do jogador).

6.1. Modelo ATACANTES usando Random Forest

O código a seguir, separa em um vetor chamado “habilidades”, qual seriam as variáveis importantes para o modelo.

habilidades <- c("crossing", "finishing", "heading_accuracy", "short_passing", "volleys", "dribbling",  "curve", "free_kick_accuracy", "long_passing",  "ball_control", "acceleration", "sprint_speed",  "agility", "reactions", "balance",  "shot_power", "jumping", "stamina",  "strength", "long_shots", "aggression",  "interceptions", "positioning", "vision",  "penalties", "composure", "marking",  "standing_tackle", "sliding_tackle", "gk_diving",  "gk_handling", "gk_kicking", "gk_positioning",  "gk_reflexes")

Então, inicialmente fazemos o modelo estatístico para identificar os atacantes que tem os valores mais divergentes de acordo com usas habilidades. O trecho de código a seguir, faz justamente isso, ou seja, de acordo com suas habilidades identifica qual seria o valor justo para os atacantes.

6.1.1 - Prepara dados

Filtra apenas jogadores de referência e armazena em referencia.df

position = "Forward"
referencia.df <- fifa %>%
  filter(league %in% best_leagues) %>%
  filter(Position == position) %>%
  select(Position, eur_value, habilidades)
#Remove casos que estejam incompletos
referencia.df <- referencia.df[complete.cases(referencia.df), ]

6.1.2 - Cria o modelo estatistico

Cria modelo usando RandomForest e armazena em referencia.rf

referencia.rf <- randomForest( eur_value~., data=referencia.df)
fifa %>%
  filter(!league %in% best_leagues) %>%
  filter(Position == position) %>%
  mutate(valor_justo = predict(referencia.rf, .),
         diferenca = eur_value - valor_justo) %>%
  select(ID, name, club, league, eur_value, valor_justo, diferenca) -> analise.atacantes

E para ter uma visão gráfica desta diferença entre valor de mercado e valor justo, temos o código a seguir que gera a figura. Nesta imagem, identificamos que o jogador “Z.Ibrahimovic” custa cerca de 18 milhões de euros a menos do que suas habilidades dizem que ele vale, logo, seria uma ótima aquisição para o clube.

plot_ly() -> fig
fig <- fig %>%
  add_trace(data = analise.atacantes, x= ~eur_value, y= ~valor_justo,
            text=~paste(name, "\nValor Merc:", sprintf("%.2f",eur_value),
                        "\nValor Justo:", sprintf("%.2f",valor_justo),
                        "\nDiferença:", sprintf("%.2f",diferenca)),
            name="Jogadores",
            type='scatter', mode='markers') %>%
  add_segments(x = 0, xend = 1e8, y = 0, yend = 1e8, name="Equilíbrio")
display.graph(fig)
# função auxiliar para exercicios 6.2 e 6.3


create_fair_value_model = . %>% 
  filter(league %in% best_leagues) %>%
  select(Position, eur_value, all_of(habilidades)) %>%
  (function (x) { x[complete.cases(x), ] }) %>%
  randomForest(eur_value~., data=.)

create_prediction_dt = function (data, rf) {
  data %>%
    filter(!league %in% best_leagues) %>%
    mutate(fair_value = predict(rf, .), gap = eur_value - fair_value) %>%
    select(ID, name, club, league, eur_value, fair_value, gap)
}

predict_fair_value = function (data, ...) {
  players = data %>% filter(Position %in% c(...))
  players.random_forest = players %>% create_fair_value_model
  players.prediction = create_prediction_dt(players,
                                            players.random_forest)
  players.prediction
}

plot_advanced_analytics = function (data, ...) {
  players.prediction = predict_fair_value(data, ...)
  plot_ly() -> fig
  fig <- fig %>%
  add_trace(data = players.prediction, x= ~eur_value, y= ~fair_value,
            text=~paste(name, "\nValor Merc:", sprintf("%.2f",eur_value),
                        "\nValor Justo:", sprintf("%.2f",fair_value),
                        "\nDiferença:", sprintf("%.2f",gap)),
            name="Jogadores",
            type='scatter', mode='markers') %>%
  add_segments(x = 0, xend = 1e8, y = 0, yend = 1e8, name="Equilíbrio")
  display.graph(fig)
}

6.2. Faça um modelo de Advanced Analytics para Meio Campistas

plot_advanced_analytics(fifa, "Outside-mid", "Center-mid")

6.3. Faça um modelo de Advanced Analytics para Zaqueiros Centrais

plot_advanced_analytics(fifa, "Outside-back", "Center-back")

7 - Conclusão

Espaço para a conclusão

f = predict_fair_value(fifa, "Forward")
m = predict_fair_value(fifa, "Outside-mid", "Center-mid")
b = predict_fair_value(fifa, "Outside-back", "Center-back")
f[order(f$gap), c("name", "gap", "club")]
m[order(m$gap), c("name", "gap", "club")]
b[order(b$gap), c("name", "gap", "club")]

Diga quais dois Atacantes poderiam ser adquiridos pelo clube e comente o motivo: Z. Ibrahimovic, Jonas

Diga quais dois Meio Campistas poderiam ser adquiridos pelo clube e comente o motivo: A. Witsel, Adrien Silva

Diga quais dois Zaqueiros Centrais poderiam ser adquiridos pelo clube e comente o motivo: P. Mertesacker, D. Srna

LS0tCnRpdGxlOiAiVHJhYmFsaG8gZW0gR3J1cG8gZGUgQW5hbHl0aWMgVG9vbHMiCmF1dGhvcjogIk1hcmNlbG8gTGVvcG9sZG8gZSBTaWx2YSBkZSBDYXJ2YWxobyBGaWxobyAtIFJNMzQyODc2IgpzdWJ0aXRsZTogQ29tbyBtb250YXIgdW0gdGltZSBkZSBmdXRlYm9sIHVzYW5kbyBBZHZhbmNlZCBBbmFseXRpY3MKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgZmlnX3dpZHRoOiAxMAogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQohW10oaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2RzLXB1YmxpY28vaW1ncy9GaWFwLWxvZ28tbm92by5qcGcpeyB3aWR0aD0yMCUgfQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBlcnJvcj1UUlVFLCB3YXJuaW5nPUZBTFNFKQpybShsaXN0ID0gbHMoKSkKYGBgCgoKCiMgMSAtIEVudGVuZGltZW50byBkbyBwcm9ibGVtYQoKCgpBIG5vc3NhIGVtcHJlc2EgZSBzZXVzIGludmVzdGlkb3JlcyBkZWNpZGlyYW0gbW9udGFyIHVtIHRpbWUgZGUgZnV0ZWJvbCBuYSBFdXJvcGEgY29tIGEgbWVzbWEgcXVhbGlkYWRlIGRvcyBtZWxob3JlcyB0aW1lcyBldXJvcGV1cyBkYSBhdHVhbGlkYWRlLiBQYXJhIGlzc28sIHNvbGljaXRhcmFtIHVtYSBhbsOhbGlzZSBkYSBlcXVpcGUgZGUgQkkgc29icmUgcXVhaXMgam9nYWRvcmVzIHBvZGVyaWFtIGFkcXVpcmlyLCBkZXNkZSBxdWUgdGl2ZXNzZW0gdW0gcHJlw6dvIGNvbXBldGl0aXZvLiBEZXZlbW9zIGNvbnRyYXRhciAxMSBqb2dhZG9yZXMgdGl0dWxhcmVzIHBhcmEgYXMgc2VndWludGVzIHBvc2nDp8O1ZXM6Cgp8UG9zacOnw6NvfERlc2NyacOnw6NvfEpvZ2Fkb3Jlc3wKfC0tLXwtLS18LS0tfAp8R0t8R29sZWlyb3wxCnxDZW50ZXItYmFja3xaYWd1ZWlybyBjZW50cmFsfDIKfE91dHNpZGUtYmFja3xaYWd1ZWlybyBsYXRlcmFsfDIKfENlbnRlci1taWR8TWVpbyBkZSBjYW1wb3wyCnxPdXRzaWRlLW1pZHxMYXRlcmFsfDIKfEZvcndhcmR8QXRhY2FudGV8MgoKQXMgcG9zacOnw7VlcyBkZXRhbGhhZGFzIHBvZGVtIHNlciBlbmNvbnRyYWRhcyBuYSBmaWd1cmEgYSBzZWd1aXI6ICAKICAKIVtdKGh0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9kcy1wdWJsaWNvL2ltZ3MvcG9zaWNvZXMuUE5HKXsgd2lkdGg9NTAlIH0gCiAKCgojIyAxLjEgRm9udGUgZGFzIGluZm9ybWHDp8O1ZXMKCkNvbW8gbsOjbyDDqSBwb3Nzw612ZWwgbWVkaXIgdG9kb3Mgb3Mgam9nYWRvcmVzIGRvIG11bmRvIGEgcGFydGlyIGRlIGFnb3JhLCBvIG5vdm8gdGltZSBjb25zaWRlcm91IGFjZWl0w6F2ZWwgdXNhciBhIGJhc2UgZGUgZGFkb3MgY29tIDE3OTk0IGpvZ2Fkb3JlcyAob3JpZ2luw6FyaW9zIGRvIGpvZ28gRmlmYSAxOCkuCgotIEFzIGluZm9ybWHDp8O1ZXMgYsOhc2ljYXMgZG9zIGpvZ2Fkb3JlcyBlc3TDo28gbm8gYXJxdWl2byBFeGNlbDogCiAgLSBodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZHMtcHVibGljby9GaWZhL2pvZ2Fkb3Jlcy54bHN4Ci0gQXMgaGFiaWxpZGFkZXMgZG9zIGpvZ2Fkb3JlcyBmb3JhbSBjYXRhbG9nYWRhcyBuYSBiYXNlIGRlIGRhZG9zOgogIC0gU2Vydmlkb3IgKGhvc3QpOiAzNS4yMjUuMjMuODkKICAtIFRpcG8gZGUgQmFuY28gZGUgZGFkb3M6IFBvc3RncmVTUUwgKHBhY290ZSBSUG9zdGdyZXMpCiAgLSBQb3J0YSAocG9ydCk6IDU1NDMyCiAgLSBEYXRhYmFzZSAoZGIpOiBmaWFwYmkKICAtIFVzdcOhcmlvICh1c2VyKTogYml1c2VyCiAgLSBTZW5oYSAocGFzc3dvcmQpOiBiaXVzZXIKICAtIFNjaGVtYSBlIFRhYmVsYTogZnV0ZWJvbC5oYWJpbGl0aWVzCi0gQXMgY2FyYWN0ZXLDrXN0aWNhcyBmw61zaWNhcyBkb3Mgam9nYWRvcmVzIGVzdMOjbyBubyBtZXNtbyBiYW5jbyBkZSBkYWRvcywgcG9yw6ltIGVtIG91dHJhIHRhYmVsYToKICAtIFNjaGVtYSBlIFRhYmVsYTogZnV0ZWJvbC5ib2R5Ci0gT3MgZGFkb3MgZmluYW5jZWlyb3MgZG9zIGpvZ2Fkb3JlcyBlc3TDo28gbm8gYXJxdWl2byBDU1YgZW5jb250cmFkbyBlbToKICAtIGh0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9kcy1wdWJsaWNvL0ZpZmEvZmluYW5jaWFsLmNzdiAKCgohW10oaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2RzLXB1YmxpY28vaW1ncy9maWZhMTgtbG9nby5qcGcpeyB3aWR0aD0xMDAlIH0KCiMjIDEuMiBEaWNpb27DoXJpbyBkZSBkYWRvcwoKLSBJbmZvcm1hw6fDtWVzIGRvcyBqb2dhZG9yZXM6ICAKSUQsIG5hbWUsIGZ1bGxfbmFtZSwgY2x1YiwgY2x1Yl9sb2dvLCBzcGVjaWFsLCBsZWFndWUsIGZsYWcsIG5hdGlvbmFsaXR5LCBwaG90bywgUG9zaXRpb24uICAKCi0gSGFiaWxpZGFkZXMgZG8gam9nYWRvciAobnVtw6lyaWNvcyk6ICAKY3Jvc3NpbmcsIGZpbmlzaGluZywgaGVhZGluZ19hY2N1cmFjeSwgc2hvcnRfcGFzc2luZywgdm9sbGV5cywgZHJpYmJsaW5nLCBjdXJ2ZSwgZnJlZV9raWNrX2FjY3VyYWN5LCBsb25nX3Bhc3NpbmcsIGJhbGxfY29udHJvbCwgYWNjZWxlcmF0aW9uLCBzcHJpbnRfc3BlZWQsIGFnaWxpdHksIHJlYWN0aW9ucywgYmFsYW5jZSwgc2hvdF9wb3dlciwganVtcGluZywgc3RhbWluYSwgc3RyZW5ndGgsIGxvbmdfc2hvdHMsIGFnZ3Jlc3Npb24sIGludGVyY2VwdGlvbnMsIHBvc2l0aW9uaW5nLCB2aXNpb24sIHBlbmFsdGllcywgY29tcG9zdXJlLCBtYXJraW5nLCBzdGFuZGluZ190YWNrbGUsIHNsaWRpbmdfdGFja2xlLCBna19kaXZpbmcsIGdrX2hhbmRsaW5nLCBna19raWNraW5nLCBna19wb3NpdGlvbmluZywgZ2tfcmVmbGV4ZXMuIAoKLSBDYXJhY3RlcsOtc3RpY2FzIGbDrXNpY2FzOiAgCk51bcOpcmljb3M6IGFnZSwgaGVpZ2h0X2NtLCB3ZWlnaHRfa2cuICAgCkNhdGVnw7NyaWNvczogYmlydGhfZGF0ZSwgYm9keV90eXBlLiAgIApCb29sZWFubzogcmVhbF9mYWNlICAKCi0gRGFkb3MgZmluYW5jZWlyb3MgKG51bcOpcmljb3MpOiAgCmV1cl92YWx1ZSwgZXVyX3dhZ2UsIGV1cl9yZWxlYXNlX2NsYXVzZSAgCgotLS0tLQoKIyAyIC0gUHJlcGFyYXRpdm9zIHBhcmEgYSBhbsOhbGlzZSAoU2V0dXApCgoKUGFyYSBpbmljaWFybW9zIGEgYW7DoWxpc2Ugw6kgcHJlY2lzbyBpbnN0YWxhciBlIGNhcnJlZ2FyIGFsZ3VucyBwYWNvdGVzLiBFc3RhIHNlw6fDo28gw6kgY29uc2lkZXJhZGEgdW0gY2FiZcOnYWxobyBlIGV2b2x1aSBjb25mb3JtZSBhIGFuw6FsaXNlIGFjb250ZWNlLgoKT2JzLjogQ2FzbyBzdWEgZXN0YcOnw6NvIG7Do28gcG9zc3VhIGFsZ3VtIGRvcyBwYWNvdGVzLCDDqSBuZWNlc3PDoXJpbyBpbnN0YWxhciBhbnRlcyEKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShEQkkpICNJbnRlcmZhY2UgcGFyYSBiYXNlIGRlIGRhZG9zCmxpYnJhcnkoUlBvc3RncmVzKSAjQ29uZXjDo28gY29tIGJhc2UgZGUgZGFkb3MKbGlicmFyeShyZWFkcikgI0xlaXR1cmEgZGUgYXJxdWl2byBDU1YKbGlicmFyeShvcGVueGxzeCkgIyBMZWl0dXJhIGRlIGFycXVpdm8gRXhjZWwKbGlicmFyeShkcGx5cikgI01hbmlwdWxhw6fDo28gZGUgZGFkb3MKbGlicmFyeShwbG90bHkpICNHcsOhZmljb3MgSW50ZXJhdGl2b3MKbGlicmFyeShyYW5kb21Gb3Jlc3QpICNBcHJlbmRpemFkbyBlc3RhdMOtc3RpY28gQWR2YW5jZWQgQW5hbHl0aWNzCmxpYnJhcnkobWFncml0dHIpCmBgYAoKCkEgc2VndWlyLCBzZXLDo28gY3JpYWRhcyBhbGd1bWFzIGZ1bsOnw7VlcyBhdXhpbGlhcmVzIHF1ZSBzZXLDo28gdXRpbGl6YWRhcyBlbSBkaXZlcnNvcyB0cmVjaG9zIGRlIGFuw6FsaXNlLgpgYGB7cn0KI08gcHJvZmVzc29yIGNyaW91IGEgZnVuw6fDo28gYWJhaXhvIHBhcmEgZGFyIG8gZGlzcGxheSBkZSB1bSBncsOhZmljbyBkbyBwbG90bHkgc2VtcHJlIGNvbSBvIG1lc21vIHRhbWFuaG8KZGlzcGxheS5ncmFwaCA8LSBmdW5jdGlvbihteWdyYXBoLCB3aWR0aCA9IDgwMCwgaGVpZ2h0ID0gNTAwLCBtYXJnaW4gPSAwKXsKICByZXQgPC0gbXlncmFwaCAlPiUgCiAgICBsYXlvdXQoYXV0b3NpemUgPSBGLCB3aWR0aCA9IHdpZHRoLCBoZWlnaHQgPSBoZWlnaHQsIG1hcmdpbiA9IG1hcmdpbikgJT4lCiAgICBjb25maWcoc2hvd0xpbmsgPSBGKQogIHJldHVybiAoIHJldCApCn0KYGBgCgoKIyAzIC0gT2J0ZW7Dp8OjbyBkZSBkYWRvcwoKQSBzZWd1aXIgdGVtb3MgZXhlbXBsb3MgZGUgb2J0ZW7Dp8OjbyBkZSBkYWRvcyBkZSBhcnF1aXZvIGNzdiwgYXJxdWl2byBFeGNlbCBlIGJhbmNvIGRlIGRhZG9zLiBVdGlsaXplIHNldSBjb25oZWNpbWVudG8gcGFyYSBvYnRlciBhcyBpbmZvcm1hw6fDtWVzIHJlcXVlcmlkYXMgaW5kaWNhZGFzIG5hIHNlw6fDo28gMToKCi0gSW5mb3JtYcOnw7VlcyBkb3Mgam9nYWRvcmVzCi0gSGFiaWxpZGFkZXMgZG8gam9nYWRvcgotIENhcmFjdGVyw61zdGljYXMgZsOtc2ljYXMKLSBEYWRvcyBmaW5hbmNlaXJvcwoKIyMgMy4xIEV4ZW1wbG9zOgpgYGB7cn0KIyBFeGVtcGxvIEV4Y2VsCmV4ZW1wbG8uY2FtZXJhcyA8LSByZWFkLnhsc3goImh0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9kcy1wdWJsaWNvL2NhbWVyYXMuYmFsdGltb3JlLnhsc3giKQoKIyBFeGVtcGxvIENTVgpleGVtcGxvLmNvcGFzIDwtIHJlYWRfY3N2KHVybCgiaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2RzLXB1YmxpY28vQ29wYXMuY3N2IikpCiMgRXhlbXBsbyBEQgpjb24gPC0gZGJDb25uZWN0KFBvc3RncmVzKCksIGhvc3Q9IjM1LjIyNS4yMy44OSIsIHBvcnQ9NTU0MzIsCiAgICAgICAgICAgICAgICAgZGI9ImZpYXBiaSIsIHVzZXI9ImJpdXNlciIsIHBhc3N3b3JkPSJiaXVzZXIiKQoKZXhlbXBsby5zdGFyd2FycyA8LSBkYkdldFF1ZXJ5KGNvbiwgIlNFTEVDVCAqIEZST00gcHVibGljLnN0YXJ3YXJzIikKZXhlbXBsby5zY2hvb2xfcGVyc29ucyA8LSBkYkdldFF1ZXJ5KGNvbiwgIlNFTEVDVCAqIEZST00gcmVwb3J0cy5zY2hvb2xfcGVyc29ucyIpCmRiRGlzY29ubmVjdChjb24pCnJtKGNvbikKYGBgCgojIyAzLjIgT2J0ZW7Dp8OjbyBkb3MgZGFkb3MKCkluc2lyYSBuYSBjw6lsdWxhIGEgc2VndWlyIG9zIHBhc3NvcyBwYXJhIG9idGVuw6fDo28gZG9zIGRhZG9zIG1lbmNpb25hZG9zLgpMZW1icmUtc2UgZGUgYXJtYXplbmFyIGVtIHZhcmnDoXZlaXMgY29tIG5vbWVzIGFwcm9wcmlhZG9zLgpgYGB7cn0KIyMgQ09MT1FVRSBTVUEgUkVTUE9TVEEgQVFVSQpwbGF5ZXJzID0gcmVhZC54bHN4KCJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZHMtcHVibGljby9GaWZhL2pvZ2Fkb3Jlcy54bHN4IikKCmNvbiA9IGRiQ29ubmVjdChQb3N0Z3JlcygpLCBob3N0PSIzNS4yMjUuMjMuODkiLCBwb3J0PTU1NDMyLCBkYj0iZmlhcGJpIiwgdXNlcj0iYml1c2VyIiwgcGFzc3dvcmQ9ImJpdXNlciIpCgpoYWJpbGl0aWVzID0gZGJHZXRRdWVyeShjb24sICJTRUxFQ1QgKiBGUk9NIGZ1dGVib2wuaGFiaWxpdGllcyIpCnBoeXNpY2FsICA9IGRiR2V0UXVlcnkoY29uLCAiU0VMRUNUICogRlJPTSBmdXRlYm9sLmJvZHkiKQoKZGJEaXNjb25uZWN0KGNvbikKcm0oY29uKQoKZmluYW5jZXMgPSByZWFkX2NzdigiaHR0cHM6Ly9zdG9yYWdlLmdvb2dsZWFwaXMuY29tL2RzLXB1YmxpY28vRmlmYS9maW5hbmNpYWwuY3N2IikKYGBgCgojIyAzLjMgSnVudGFyIHRvZG9zIG9zIGRhZG9zIGVtICoqZmlmYSoqClBhcmEgZmFjaWxpdGFyIHRvZGFzIGFzIGFuw6FsaXNlcyBhIHNlZ3VpciwgY3JpZSB1bWEgdGFiZWxhIGNoYW1hZGEgYGZpZmFgLCBjb20gbyAiam9pbiIgZGUgdG9kYXMgYXMgdGFiZWxhcyAodXNlIGBpbm5lcl9qb2luYCkKYGBge3J9CiMjIENPTE9RVUUgU1VBIFJFU1BPU1RBIEFRVUkKZmlmYSA9IHBsYXllcnMgJT4lIAogICAgICAgaW5uZXJfam9pbihoYWJpbGl0aWVzLGJ5PSJJRCIpICU+JSAKICAgICAgIGlubmVyX2pvaW4ocGh5c2ljYWwsIGJ5PSJJRCIpICU+JQogICAgICAgaW5uZXJfam9pbihmaW5hbmNlcywgYnk9IklEIikKYGBgCgojIyAzLjQgVmFsaWRhw6fDtWVzOgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KaWYoISdmaWZhJyAlaW4lIGxzKCkpewogIHByaW50KCJRdWVyaWRvIGFsdW5vLCBhIHRhYmVsYSAnZmlmYScgcHJlY2lzYSBleGlzdGlyISIpCn0gZWxzZSB7CiAgcHJpbnQoIlBhcmVjZSBxdWUgZXN0w6EgY2VydG8sIHZhbW9zIHNlZ3VpciEiKQp9CmBgYAoKCiMgNCAtIEFuw6FsaXNlIGV4cGxvcmF0w7NyaWEgaW5pY2lhbAoKIyMgNC4xCUNvbmhlY2ltZW50byBpbmljaWFsIGRvcyBkYWRvcwoKVG9kbyBwcm9jZXNzYW1lbnRvIGRldmUgc2UgaW5pY2lhciBkZSB1bWEgYW7DoWxpc2UgZXhwbG9yYXTDs3JpYSwgb3Ugc2VqYSwgY29uaGVjZXIgb3MgZGFkb3MgcXVlIHRlbW9zIGRpc3BvbsOtdmVpcy4KClBhcmEgaXNzbywgdmFtb3MgdXNhciBhbGd1bnMgY29uY2VpdG9zIGRlIGVzdGF0w61zdGljYSBkZXNjcml0aXZhLgoKT3MgY8OzZGlnb3MgYSBzZWd1aXIgZXhpYmVtIGluZm9ybWHDp8O1ZXMgc29icmUgdW1hIHRhYmVsYSBjaGFtYWRhIGBtdGNhcnNgLCBlbnRyZSBlc3RlcyBkYWRvczogIAotIFF1YW50aWRhZGUgZGUgbGluaGFzIGUgY29sdW5hczsgIAotIFJlc3Vtb3MgZXN0YXTDrXN0aWNvcyBkYXMgY29sdW5hczsgIAotIFByaW1laXJvcyByZWdpc3Ryb3MgZGEgdGFiZWxhLiAgCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmRpbShtdGNhcnMpCnN1bW1hcnkobXRjYXJzKQpoZWFkKG10Y2FycykKYGBgCgpVc2UgbyBleGVtcGxvIGFjaW1hIGUgaW5mb3JtZSA6CgpgYGB7cn0KZGltKGZpZmEpCnN1bW1hcnkoZmlmYSkKYGBgCgotIFF1YWwgYSBxdWFudGlkYWRlIGRlIGpvZ2Fkb3JlcyBubyBhcnF1aXZvIGRlIGpvZ2Fkb3JlczogMTc5OTQKCi0gUXVhbCBvIHNhbMOhcmlvIG3DqWRpbyBkb3Mgam9nYWRvcmVzLCBlbSBFdXJvOiAxMTUwNAoKCgoKIyMgNC4yIExpZ2FzIGRpc3BvbsOtdmVpcyBlIGxpZ2FzIGRlIHJlZmVyw6puY2lhCgpPIGV4ZW1wbG8gYWJhaXhvIGV4aWJlIHF1YW50YXMgZXNww6ljaWVzIGRpZmVyZW50ZXMgdGVtb3MgbmEgdGFiZWxhIGBleGVtcGxvLnN0YXJ3YXJzYApgYGB7cn0KbGVuZ3RoKCB1bmlxdWUoZXhlbXBsby5zdGFyd2FycyRzcGVjaWVzKSApCmBgYAoKQmFzZWFkbyBuaXNzbywgcXVhbnRvcyBDbHViZXMgZSBxdWFudGFzIGxpZ2FzIHRlbW9zIGRpc3BvbsOtdmVpczoKCmBgYHtyfQpsZW5ndGgodW5pcXVlKGZpZmEkY2x1YikpCmxlbmd0aCh1bmlxdWUoZmlmYSRsZWFndWUpKQpgYGAKCi0gQ2x1YmVzOiA2NDgKLSBMaWdhczogIDQyCgoKUXVhbnRvcyBqb2dhZG9yZXMgdGVtb3MgZW0gY2FkYSBsaWdhPyBVc2UgbyBleGVtcGxvIGFiYWl4byBwYXJhIHJlc3BvbmRlci4KYGBge3J9CmV4ZW1wbG8uc3RhcndhcnMgJT4lCiAgZ3JvdXBfYnkoc3BlY2llcykgJT4lCiAgc3VtbWFyaXNlKFBlcnNvbmFnZW5zID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MoUGVyc29uYWdlbnMpKQpgYGAKClJlcG9zdGE6CmBgYHtyfQpmaWZhICU+JQogIGdyb3VwX2J5KGxlYWd1ZSkgJT4lCiAgc3VtbWFyaXNlKHBsYXllcnMgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhwbGF5ZXJzKSkKYGBgCgoKIyMgNC4zIExpZ2EgZGUgcmVmZXLDqm5jaWEhIChhcyBtZWxob3Jlcywgc2VndW5kbyBvIHByZWZlc3NvcikgIQpOb3NzYSBiYXNlIGRlIGRhZG9zIHBvc3N1aSA2NDcgdGltZXMgZGl2aWRpZG9zIGVtIDQxIGxpZ2FzLiBBcyBzZWd1aW50ZXMgbGlnYXMgc2Vyw6NvIHNlbGVjaW9uYWRhcyBjb21vIGFzIG1lbGhvcmVzIGxpZ2FzIHBhcmEgYW7DoWxpc2UgZG9zIGpvZ2Fkb3JlczogIAotIEVuZ2xpc2ggQ2hhbXBpb25zaGlwICAKLSBGcmVuY2ggTGlndWUgMSAgCi0gR2VybWFuIEJ1bmRlc2xpZ2EgIAotIFNwYW5pc2ggUHJpbWVyYSBEaXZpc2nDs24gIAotIEl0YWxpYW4gU2VyaWUgQSAob2ssIHNlaSBxdWUgaMOhIGNvbnRyb3bDqXJzaWFzKSAgCgpQYXJhIGZhY2lsaWFyIG5vc3NvIHRyYWJhbGhvIGZ1dHVybywgYXJtYXplbmFtb3MgYXMgbGlnYXMgZGUgcmVmZXLDqm5jaWEgc2VsZWNpb25hZGFzIG5vIHZldG9yIGBiZXN0X2xlYWd1ZXNgOgpgYGB7cn0KYmVzdF9sZWFndWVzID0gYygiRW5nbGlzaCBDaGFtcGlvbnNoaXAiLCAiRnJlbmNoIExpZ3VlIDEiLCAiR2VybWFuIEJ1bmRlc2xpZ2EiLCAiU3BhbmlzaCBQcmltZXJhIERpdmlzacOzbiIsICJJdGFsaWFuIFNlcmllIEEiKQpiZXN0X3NwZWNpZXMgPSBjKCJEcm9pZCIsICJNaXJpYWxhbiIpCmBgYAoKQWdvcmEgZXhpYmEgdW0gZ3LDoWZpY28gbm9zIGluZm9ybWFuZG8gcXVhbnRvcyBqb2dhZG9yZXMgZXN0w6NvIG5hIGxpZ2EgZGUgcmVmZXLDqm5jaWEuIFZlamEgbyBleGVtcGxvIGFiYWl4byBxdWUgZXhpYmUgcXVhbnRvcyBwZXJzb25hZ2VzIGVzdMOjbyBuYXMgZXNww6ljaWVzIGRlIHJlZmVyw6puY2lhOgpgYGB7cn0KZXhlbXBsby5zdGFyd2FycyAlPiUKICBncm91cF9ieShzcGVjaWVzKSAlPiUKICBzdW1tYXJpc2UoUGVyc29uYWdlbnMgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhQZXJzb25hZ2VucykpICU+JQogIG11dGF0ZSggUmVmZXJlbmNpYSA9IHNwZWNpZXMgJWluJSBiZXN0X3NwZWNpZXMpIC0+IGV4ZW1wbG8uYWdyZWdhY2FvCnByaW50KGV4ZW1wbG8uYWdyZWdhY2FvKQpgYGAKCmBgYHtyfQojIyBDT0xPUVVFIFNVQSBSRVNQT1NUQSBBUVVJCm5fcGxheWVyc19ieV9sZWFndWUgPSBmaWZhICU+JQogIGdyb3VwX2J5KGxlYWd1ZSkgJT4lCiAgc3VtbWFyaXNlKHBsYXllcnMgPSBuKCkpICU+JQogIGFycmFuZ2UoZGVzYyhwbGF5ZXJzKSkgJT4lCiAgbXV0YXRlKHJlZmVyZW5jZSA9IGxlYWd1ZSAlaW4lIGJlc3RfbGVhZ3VlcykgJVQ+JQogIHByaW50KCkKYGBgCgoKIyMgNC40IEdyw6FmaWNvIGRlIGJhcnJhcwpFeGliYSBvIGdyw6FmaWNvIGRlIGJhcnJhcyBkZSB0b2RhcyBhcyBsaWdhcywgZGVzdGFjYW5kbyBhcyBxdWUgc8OjbyBsaWdhcyBkZSByZWZlcsOqbmNpYS4gVXNlIG8gZXhlbXBsbyBhYmFpeG86IApgYGB7ciB3YXJuaW5nPUZBTFNFLCBvdXQud2lkdGg9IjEwMCUifQpncmFmaWNvIDwtIGV4ZW1wbG8uYWdyZWdhY2FvICU+JQogIGZpbHRlcihSZWZlcmVuY2lhID09IEYpICU+JQogIHBsb3RfbHkoeCA9IH5zcGVjaWVzLCB5ID0gflBlcnNvbmFnZW5zLCBjb2xvciA9IH5SZWZlcmVuY2lhLCB0eXBlID0gJ2JhcicsIG5hbWU9Ik91dHJhcyIpCmdyYWZpY28gPC0gZ3JhZmljbyAlPiUgYWRkX2JhcnMoeCA9IH5zcGVjaWVzLCB5ID0gflBlcnNvbmFnZW5zLCBjb2xvciA9IH5SZWZlcmVuY2lhLCBkYXRhID0gZXhlbXBsby5hZ3JlZ2FjYW9bZXhlbXBsby5hZ3JlZ2FjYW8kUmVmZXJlbmNpYT09VCxdLCBuYW1lPSJCZXN0IikKCgpkaXNwbGF5LmdyYXBoKGdyYWZpY28pCmBgYAoKTWluaGFzIGxpZ2FzLCBzZSB0dWRvIGRlciBjZXJ0bywgdW0gZ3LDoWZpY28gY29tbyBlc3RlIGRldmUgc2VyIGV4aWJpZG86CiFbXShodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZHMtcHVibGljby9GaWZhL0Jlc3RMZWFndWVzLnBuZykKCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBvdXQud2lkdGg9IjEwMCUifQpuX3BsYXllcnNfYnlfbGVhZ3VlICU+JQogIGZpbHRlcihyZWZlcmVuY2UgPT0gRkFMU0UpICU+JQogIHBsb3RfbHkoeCA9IH5sZWFndWUsIHkgPSB+cGxheWVycywgY29sb3IgPSB+cmVmZXJlbmNlLCB0eXBlID0gJ2JhcicsIG5hbWUgPSAnb3RoZXInKSAlPiUKICBhZGRfYmFycyh4ID0gfmxlYWd1ZSwgeSA9IH5wbGF5ZXJzLCBjb2xvciA9IH5yZWZlcmVuY2UsIGRhdGEgPSBmaWx0ZXIobl9wbGF5ZXJzX2J5X2xlYWd1ZSwgcmVmZXJlbmNlID09IFRSVUUpLCBuYW1lID0gImJlc3QiKSAlVD4lIGRpc3BsYXkuZ3JhcGgoKQpgYGAKCgojIDUgLSBBbsOhbGlzZSBDYXJhY3RlcsOtc3RpY2FzIGbDrXNpY2FzIGRvcyBqb2dhZG9yZXMKClNlcsOhIHF1ZSBow6EgZGlmZXJlbsOnYXMgZsOtc2ljYXMgZW50cmUgam9nYWRvcmVzIGRhcyBsaWdhcyBkaWZlcmVudGVzPwpWZWphIG8gZXhlbXBsbyBhIHNlZ3VpcjoKYGBge3J9CnBhcihtZnJvdz1jKDIsMSkpCmV4ZW1wbG8uc2Nob29sX3BlcnNvbnMgJT4lCiAgZmlsdGVyKGdlbmRlcj09Im1lbiIpICU+JQogIC4kZWFybmluZ3MgJT4lCiAgaGlzdCh4bGltPWMoMCwgMzAwKSwgbWFpbj0iSG9tZW5zIikKZXhlbXBsby5zY2hvb2xfcGVyc29ucyAlPiUKICBmaWx0ZXIoZ2VuZGVyPT0id29tZW4iKSAlPiUKICAuJGVhcm5pbmdzICU+JQogIGhpc3QoeGxpbT1jKDAsIDMwMCksIG1haW49Ik11bGhlcmVzIikKcGFyKG1mcm93PWMoMSwxKSkKYGBgCkFnb3JhIG1vc3RyZSBhIGRpc3RyaWJ1acOnw6NvIGRlIGFsdHVyYSBkb3MgQVRBQ0FOVEVTICggYEZvcndhcmRgKSBkYXMgbWVsaG9yZXMgbGlnYXMgYWNpbWEgZSBkYXMgZGVtYWlzIGxpZ2FzIGFiYWl4by4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGFyKG1mcm93PWMoMiwxKSkKZm9yd2FyZHMgPSBmaWx0ZXIoZmlmYSwgUG9zaXRpb24gPT0gIkZvcndhcmQiKQpmb3J3YXJkcyAlPiUgZmlsdGVyKG5vdChsZWFndWUgJWluJSBiZXN0X2xlYWd1ZXMpKSAlPiUgLiRoZWlnaHRfY20gJT4lIGhpc3QobWFpbiA9ICJvdGhlciIsIHhsaW09YygxNTAsIDIxMCksIGJyZWFrcyA9IDIwKQpmb3J3YXJkcyAlPiUgZmlsdGVyKGxlYWd1ZSAlaW4lIGJlc3RfbGVhZ3VlcykgJT4lIC4kaGVpZ2h0X2NtICU+JSBoaXN0KG1haW4gPSAiYmVzdCIsIHhsaW09YygxNTAsIDIxMCksIGJyZWFrcz0gMjApCnBhcihtZnJvdz1jKDEsMSkpCmBgYApFeGVtcGxvIGRlIHVtYSBpbWFnZW0gZXNwZXJhZGE6CiFbXShodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZHMtcHVibGljby9GaWZhL0FsdHVyYVBvclRvcExlYWd1ZS5wbmcpCgoKCiMjIDUuMiBFWFRSQSEgRXhlbXBsbyBkZSBhbsOhbGlzZXMgbWFpcyBkZXRhbGhhZGFzLgogCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmlmKCEnZmlmYScgJWluJSBscygpKXsKICBmaWZhIDwtIHJlYWRfY3N2KCJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20vZHMtcHVibGljby9maWZhJTIwZ2FtZS0zLmNzdiIsCiAgICAgICAgICAgICAgICAgICBsb2NhbGUgPSBsb2NhbGUoZW5jb2RpbmcgPSAiVVRGLTgiKSkKfQpmaWZhIDwtIGZpZmEgJT4lCiAgbXV0YXRlKFNlbGVjdGVkID0gbGVhZ3VlICVpbiUgYmVzdF9sZWFndWVzKQoKYW5hbGlzZV9wb3NpY2FvIDwtIGZ1bmN0aW9uKGNvbHVuYSwgbm9tZV9jb2wpewogIGZpZyA8LSBmaWZhICU+JSBwbG90X2x5KHR5cGUgPSAndmlvbGluJykgCiAgCiAgZmlnIDwtIGZpZyAlPiUKICAgIGFkZF90cmFjZSgKICAgICAgeCA9IH5Qb3NpdGlvbltmaWZhJFNlbGVjdGVkID09IFRdLAogICAgICB5ID0gfmNvbHVuYVtmaWZhJFNlbGVjdGVkID09IFRdLAogICAgICBsZWdlbmRncm91cCA9ICdUb3AnLAogICAgICBzY2FsZWdyb3VwID0gJ1RvcCcsCiAgICAgIG5hbWUgPSAnVG9wJywKICAgICAgc2lkZSA9ICduZWdhdGl2ZScsCiAgICAgIGJveCA9IGxpc3QoIHZpc2libGUgPSBUICksCiAgICAgIG1lYW5saW5lID0gbGlzdCggdmlzaWJsZSA9IFQgKSwKICAgICAgY29sb3IgPSBJKCJibHVlIikKICAgICkgCiAgZmlnIDwtIGZpZyAlPiUKICAgIGFkZF90cmFjZSgKICAgICAgeCA9IH5Qb3NpdGlvbltmaWZhJFNlbGVjdGVkID09IEZdLAogICAgICB5ID0gfmNvbHVuYVtmaWZhJFNlbGVjdGVkID09IEZdLAogICAgICBsZWdlbmRncm91cCA9ICdPdXRyYXMnLAogICAgICBzY2FsZWdyb3VwID0gJ091dHJhcycsCiAgICAgIG5hbWUgPSAnT3V0cmFzJywKICAgICAgc2lkZSA9ICdwb3NpdGl2ZScsCiAgICAgIGJveCA9IGxpc3QoIHZpc2libGUgPSBUICksCiAgICAgIG1lYW5saW5lID0gbGlzdCggdmlzaWJsZSA9IFQgKSwKICAgICAgY29sb3IgPSBJKCIjNDQ5OTQ0IikKICAgICkgCiAgCiAgZmlnIDwtIGZpZyAlPiUKICAgIGxheW91dCgKICAgICAgeGF4aXMgPSBsaXN0KCB0aXRsZSA9ICJQb3Npw6fDo28iICksCiAgICAgIHlheGlzID0gbGlzdCggdGl0bGUgPSBub21lX2NvbCwgemVyb2xpbmUgPSBGICksCiAgICAgIHZpb2xpbmdhcCA9IDAsCiAgICAgIHZpb2xpbmdyb3VwZ2FwID0gMCwKICAgICAgdmlvbGlubW9kZSA9ICdvdmVybGF5JwogICAgKQogIAogIHJldHVybihkaXNwbGF5LmdyYXBoKGZpZykpCn0KYW5hbGlzZV9wb3NpY2FvKGZpZmEkYWdlLCAiSWRhZGUiKQphbmFsaXNlX3Bvc2ljYW8oZmlmYSRoZWlnaHRfY20sICJBbHR1cmEgKGNtKSIpCmFuYWxpc2VfcG9zaWNhbyhmaWZhJHdlaWdodF9rZywgIlBlc28gKGtnKSIpCmFuYWxpc2VfcG9zaWNhbyhmaWZhJGV1cl92YWx1ZSwgIlZhbG9yIGRlIGNvbXByYShFVVIpIikKYW5hbGlzZV9wb3NpY2FvKGZpZmEkZXVyX3dhZ2UsICJTYWzDoXJpbyAoRVVSKSIpCmBgYAoKRSBlbnTDo28gcG9kZW1vcyBmYXplciBhcyBhbsOhbGlzZXMgc29icmUgZXNzYXMgY2FyYWN0ZXLDrXN0aWNhcy4gTmFzIGZpZ3VyYXMgcG9kZW1vcyB2ZXIgcXVlIG9zIGpvZ2Fkb3JlcyBkYXMgbGlnYXMgc2VsZWNpb25hZGFzIHPDo28gbWFpcyBub3ZvcyBkbyBxdWUgZGFzIGRlbWFpcyBsaWdhcywgaW5kZXBlbmRlbnRlcyBkYSBwb3Npw6fDo28gZW5xdWFudG8gcXVlIG9zIGpvZ2Fkb3JlcyBkYXMgbGlnYXMgbsOjbyBzZWxlY2lvbmFkYXMgdGVtIHVtIHNhbMOhcmlvIG1lbm9yLCBpbmRlcGVuZGVudGVtZW50ZSBkYSBwb3Npw6fDo28uIE8gaW50ZXJlc3NhbnRlIGRlc3NhcyBhbsOhbGlzZXMgw6kgcXVlIMOpIHBvc3PDrXZlbCB2ZXIgYSBlc3RhdMOtc3RpY2EgZGVzY3JpdGl2YSBkZSB1bWEgZm9ybWEgbXVpdG8gbWFpcyBuYXR1cmFsLCBpbmNsdWluZG8gbcOpZGlhcywgbWVkaWFuYXMsIHF1YXJ0aXMsIG3DoXhpbW9zIGUgbcOtbmltb3MsIGFsw6ltIGRlIHVtYSBjdXJ2YSBkZSBkaXN0cmlidWnDp8Ojby4gIAoKQ29tbyBvIG9iamV0aXZvIMOpIG1vc3RyYXIgbyBwb2RlciBkYSBmZXJyYW1lbnRhLCBkZSBmb3JtYSBuw6NvIGV4YXVzdGl2YSwgdm91IHBhcmFyIGEgYW7DoWxpc2UgZXhwbG9yYXTDs3JpYSBwb3IgYXF1aS4gUGVyY2ViYW0gcXVlIGNvbSBwb3VjYXMgbGluaGFzIGRlIGPDs2RpZ28sIGNvbnNlZ3VpbW9zIGdlcmFyIGdyw6FmaWNvcyBpbnRlcmF0aXZvcyB0b3RhbG1lbnRlIGN1c3RvbWl6YWRhcy4gQWdvcmEsIGRlaXhhcmVpIGFzIGRlbWFpcyBhbsOhbGlzZXMgZXhwbG9yYXTDs3JpYXMgcGFyYSB2b2PDqnMgZSBwYXJ0aXIgcGFyYSBhIHByb3Bvc3RhIGRlIHJlc29sdcOnw6NvIGRvIHByb2JsZW1hOiBFc2NvbGhlciBub3Nzb3MgMTEgam9nYWRvcmVzLCBib25zIGUgYmFyYXRvcyEKCgojIDYgLSBQcm9wb3N0YSBkZSByZXNvbHXDp8OjbyBkbyBwcm9ibGVtYQoKUGFyYSByZXNvbHZlciBlc3RlIHByb2JsZW1hLCB2YW1vcyB0ZW50YXIgb2J0ZXIgbyBwcmXDp28ganVzdG8gZG8gam9nYWRvciAoZXVyX3ZhbHVlKSwgYmFzZWFkbyBhcGVuYXMgZW0gc3VhcyBoYWJpbGlkYWRlcywgaWRlbnRpZmljYWRhcyBubyBkaWNpb27DoXJpbyBkZSBkYWRvcy4gIAoKU2UgZXN0aXbDqXNzZW1vcyBvbGhhbmRvIGFwZW5hcyB1bWEgaGFiaWxpZGFkZSwgY29tbyBkcmliYmxpbmcsIHBhcmEgYXRhY2FudGVzLCBwb3IgZXhlbXBsbywgcG9kZXLDrWFtb3MgdHJhw6dhciB1bWEgbcOpZGlhLCBkZSBhY29yZG8gY29tIGVzc2EgaGFiaWxpZGFkZSwgY29ycmV0bz8gTmVzdGUgY2FzbywgcG9kZXLDrWFtb3MgdHJhw6dhciB1bSBncsOhZmljbyBjb21vIGVzdGUsIHByZXNlbnRlIG5hIGEgc2VndWlyLCBlbSBxdWUgY2FkYSBwb250byBhenVsIHJlcHJlc2VudGEgdW0gYXRhY2FudGUsIGUgY2FkYSBwb250byBsYXJhbmphIG8gc2Fsw6FyaW8gbcOpZGlvIGRvcyBhdGFjYW50ZXMgcXVlIHBvc3N1ZW0gYSBtZXNtYSBub3RhIGRlIGRyaWJibGluZy4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgQ0VMVUxBIEFQRU5BUyBQQVJBIERFTU9OU1RSQUNBTwoKI09idMOpbSBqb2dhZG9yZXMgZGUgcmVmZXLDqm5jaWEgZSBhcm1hemVuYSBlbSBqX3JlZgpqX3JlZiA8LSBmaWZhICU+JQogIGZpbHRlcihQb3NpdGlvbiA9PSAiRm9yd2FyZCIpICU+JQogIGZpbHRlcihsZWFndWUgJWluJSBiZXN0X2xlYWd1ZXMpCgojIE9idGVtIGEgbWVkaWEgZGVzdGVzIGpvZ2Fkb3JlcywgUE9SIGRyaWJibGluZyBlIGFybWF6ZW5hIGVtIHZsbWVkaW8KdmxtZWRpbyA8LSBqX3JlZiAlPiUKICBncm91cF9ieShkcmliYmxpbmcpICU+JQogIHN1bW1hcmlzZShldXJfdmFsdWUgPSBtZWFuKGV1cl92YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgbXV0YXRlKGRlc2NyaWNhbyA9IHBhc3RlKCJNw6lkaWEgZGUgZHJpYmJsaW5nICIsIGRyaWJibGluZykgKSAlPiUKICBzZWxlY3QoZHJpYmJsaW5nLCBkZXNjcmljYW8sIGV1cl92YWx1ZSkKICAKI0NyaWEgZ3LDoWZpY28gZG9zIGpvZ2Fkb3JlcwpmaWcgPC0gcGxvdF9seShqX3JlZiwgeCA9IH5kcmliYmxpbmcsIHkgPSB+ZXVyX3ZhbHVlLAogICAgICAgICAgICAgICB0ZXh0PX5mdWxsX25hbWUsIHR5cGU9J3NjYXR0ZXInLCBtb2RlPSdtYXJrZXJzJywKICAgICAgICAgICAgICAgbmFtZT0iSm9nYWRvcmVzIikKCiMgQWRpY2lvbmEgYXMgbcOpZGlhcwpmaWcgPC0gYWRkX3RyYWNlKGZpZywgZGF0YT12bG1lZGlvLAogICAgICAgICAgICAgICAgIHggPSB+ZHJpYmJsaW5nLCB5ID0gfmV1cl92YWx1ZSwKICAgICAgICAgICAgICAgICB0eXBlPSdzY2F0dGVyJywgbW9kZT0nbWFya2VycycsIHRleHQ9IH4gcGFzdGUoIk1lZGlhIGRlIiwgZHJpYmJsaW5nKSwKICAgICAgICAgICAgICAgICBuYW1lPSJNw6lkaWFzIiApCgpkaXNwbGF5LmdyYXBoKGZpZykKYGBgCgpNYXMgY29tbyBmYXplciBpc3NvIGNvbnNpZGVyYW5kbyBtYWlzIGRvIHF1ZSB1bWEgaGFiaWxpZGFkZT8gRSBzZSBxdWlzZXJtb3MgY29uc2lkZXJhciB0b2RhcyBhcyBoYWJpbGlkYWRlcyBwYXJhIGRlZmluaXIgbyBwcmXDp28ganVzdG8gZG8gam9nYWRvcj8gIAoKU2ltLCBpc3NvIG7Do28gYXBlbmFzIMOpIHBvc3PDrXZlbCBjb21vIGV4aXN0ZW0gbXVpdGFzIHTDqWNuaWNhcyBwb3Nzw612ZWlzIHBhcmEgc2UgY2hlZ2FyIGEgZXN0ZSByZXN1bHRhZG8uIMOJIHByZWNpc28gZGUgdW0gcG91Y28gZGUgY29uaGVjaW1lbnRvIGVzdGF0w61zdGljbyBvdSBkZSBhcHJlbmRpemFkbyBkZSBtw6FxdWluYSwgbWFzIHV0aWxpemFyIHVtYSBmZXJyYW1lbnRhIGNvbW8gbyBSIMOpIG8gcXVlIGRpZmVyZW5jaWEgZ3JhbmRlcyBhbsOhbGlzZXMsIHBvZGVuZG8gdWx0cmFwYXNzYXIgYXMgYW7DoWxpc2VzIGRvcyBzaW1wbGVzIHJlbGF0w7NyaW9zIGV4cGxpY2F0aXZvcy4gIAoKUGFyYSBlc3RlIGNhc28sIHZvdSB1c2FyIHVtIGFsZ29yaXRtbyBkZSBCcmVpbWFuIGUgQ3V0bGVyIGNoYW1hZG8gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIFJlZ3Jlc3PDo28uIFRyYXRhLXNlIGRlIHVtIG1vZGVsbyBkZSBhcHJlbmRpemFkbyBlc3RhdMOtc3RpY28gcXVlIGF2YWxpYSBhcyBkaXZlcnNhcyBjYXJhY3RlcsOtc3RpY2FzIChoYWJpbGlkYWRlcyBkbyBqb2dhZG9yKSBlIGVzdGFiZWxlY2UgcXVhbCBzZXJpYSBhIHJlc3Bvc3RhIGRvIG1vZGVsbyAodmFsb3IgZG8gam9nYWRvcikuICAKCiMjIDYuMS4JTW9kZWxvIEFUQUNBTlRFUyB1c2FuZG8gUmFuZG9tIEZvcmVzdAoKTyBjw7NkaWdvIGEgc2VndWlyLCBzZXBhcmEgZW0gdW0gdmV0b3IgY2hhbWFkbyDigJxoYWJpbGlkYWRlc+KAnSwgcXVhbCBzZXJpYW0gYXMgdmFyacOhdmVpcyBpbXBvcnRhbnRlcyBwYXJhIG8gbW9kZWxvLiAgCgpgYGB7cn0KaGFiaWxpZGFkZXMgPC0gYygiY3Jvc3NpbmciLCAiZmluaXNoaW5nIiwgImhlYWRpbmdfYWNjdXJhY3kiLCAic2hvcnRfcGFzc2luZyIsICJ2b2xsZXlzIiwgImRyaWJibGluZyIsICAiY3VydmUiLCAiZnJlZV9raWNrX2FjY3VyYWN5IiwgImxvbmdfcGFzc2luZyIsICAiYmFsbF9jb250cm9sIiwgImFjY2VsZXJhdGlvbiIsICJzcHJpbnRfc3BlZWQiLCAgImFnaWxpdHkiLCAicmVhY3Rpb25zIiwgImJhbGFuY2UiLCAgInNob3RfcG93ZXIiLCAianVtcGluZyIsICJzdGFtaW5hIiwgICJzdHJlbmd0aCIsICJsb25nX3Nob3RzIiwgImFnZ3Jlc3Npb24iLCAgImludGVyY2VwdGlvbnMiLCAicG9zaXRpb25pbmciLCAidmlzaW9uIiwgICJwZW5hbHRpZXMiLCAiY29tcG9zdXJlIiwgIm1hcmtpbmciLCAgInN0YW5kaW5nX3RhY2tsZSIsICJzbGlkaW5nX3RhY2tsZSIsICJna19kaXZpbmciLCAgImdrX2hhbmRsaW5nIiwgImdrX2tpY2tpbmciLCAiZ2tfcG9zaXRpb25pbmciLCAgImdrX3JlZmxleGVzIikKCmBgYAoKRW50w6NvLCBpbmljaWFsbWVudGUgZmF6ZW1vcyBvIG1vZGVsbyBlc3RhdMOtc3RpY28gcGFyYSBpZGVudGlmaWNhciBvcyBhdGFjYW50ZXMgcXVlIHRlbSBvcyB2YWxvcmVzIG1haXMgZGl2ZXJnZW50ZXMgZGUgYWNvcmRvIGNvbSB1c2FzIGhhYmlsaWRhZGVzLiBPIHRyZWNobyBkZSBjw7NkaWdvIGEgc2VndWlyLCBmYXoganVzdGFtZW50ZSBpc3NvLCBvdSBzZWphLCBkZSBhY29yZG8gY29tIHN1YXMgaGFiaWxpZGFkZXMgaWRlbnRpZmljYSBxdWFsIHNlcmlhIG8gdmFsb3IganVzdG8gcGFyYSBvcyAqKmF0YWNhbnRlcyoqLiAgICAKCiMjIDYuMS4xIC0gUHJlcGFyYSBkYWRvcyAKRmlsdHJhIGFwZW5hcyBqb2dhZG9yZXMgZGUgcmVmZXLDqm5jaWEgZSBhcm1hemVuYSBlbSByZWZlcmVuY2lhLmRmCmBgYHtyfQpwb3NpdGlvbiA9ICJGb3J3YXJkIgpyZWZlcmVuY2lhLmRmIDwtIGZpZmEgJT4lCiAgZmlsdGVyKGxlYWd1ZSAlaW4lIGJlc3RfbGVhZ3VlcykgJT4lCiAgZmlsdGVyKFBvc2l0aW9uID09IHBvc2l0aW9uKSAlPiUKICBzZWxlY3QoUG9zaXRpb24sIGV1cl92YWx1ZSwgaGFiaWxpZGFkZXMpCiNSZW1vdmUgY2Fzb3MgcXVlIGVzdGVqYW0gaW5jb21wbGV0b3MKcmVmZXJlbmNpYS5kZiA8LSByZWZlcmVuY2lhLmRmW2NvbXBsZXRlLmNhc2VzKHJlZmVyZW5jaWEuZGYpLCBdCmBgYAoKIyA2LjEuMiAtIENyaWEgbyBtb2RlbG8gZXN0YXRpc3RpY28KQ3JpYSBtb2RlbG8gdXNhbmRvIFJhbmRvbUZvcmVzdCBlIGFybWF6ZW5hIGVtIHJlZmVyZW5jaWEucmYKCmBgYHtyfQpyZWZlcmVuY2lhLnJmIDwtIHJhbmRvbUZvcmVzdCggZXVyX3ZhbHVlfi4sIGRhdGE9cmVmZXJlbmNpYS5kZikKYGBgCgpgYGB7cn0KZmlmYSAlPiUKICBmaWx0ZXIoIWxlYWd1ZSAlaW4lIGJlc3RfbGVhZ3VlcykgJT4lCiAgZmlsdGVyKFBvc2l0aW9uID09IHBvc2l0aW9uKSAlPiUKICBtdXRhdGUodmFsb3JfanVzdG8gPSBwcmVkaWN0KHJlZmVyZW5jaWEucmYsIC4pLAogICAgICAgICBkaWZlcmVuY2EgPSBldXJfdmFsdWUgLSB2YWxvcl9qdXN0bykgJT4lCiAgc2VsZWN0KElELCBuYW1lLCBjbHViLCBsZWFndWUsIGV1cl92YWx1ZSwgdmFsb3JfanVzdG8sIGRpZmVyZW5jYSkgLT4gYW5hbGlzZS5hdGFjYW50ZXMKYGBgCgoKRSBwYXJhIHRlciB1bWEgdmlzw6NvIGdyw6FmaWNhIGRlc3RhIGRpZmVyZW7Dp2EgZW50cmUgdmFsb3IgZGUgbWVyY2FkbyBlIHZhbG9yIGp1c3RvLCB0ZW1vcyBvIGPDs2RpZ28gYSBzZWd1aXIgcXVlIGdlcmEgYSBmaWd1cmEuIE5lc3RhIGltYWdlbSwgaWRlbnRpZmljYW1vcyBxdWUgbyBqb2dhZG9yICJaLklicmFoaW1vdmljIiBjdXN0YSBjZXJjYSBkZSAxOCBtaWxow7VlcyBkZSBldXJvcyBhIG1lbm9zIGRvIHF1ZSBzdWFzIGhhYmlsaWRhZGVzIGRpemVtIHF1ZSBlbGUgdmFsZSwgbG9nbywgc2VyaWEgdW1hIMOzdGltYSBhcXVpc2nDp8OjbyBwYXJhIG8gY2x1YmUuICAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcGxvdF9seSgpIC0+IGZpZwpmaWcgPC0gZmlnICU+JQogIGFkZF90cmFjZShkYXRhID0gYW5hbGlzZS5hdGFjYW50ZXMsIHg9IH5ldXJfdmFsdWUsIHk9IH52YWxvcl9qdXN0bywKICAgICAgICAgICAgdGV4dD1+cGFzdGUobmFtZSwgIlxuVmFsb3IgTWVyYzoiLCBzcHJpbnRmKCIlLjJmIixldXJfdmFsdWUpLAogICAgICAgICAgICAgICAgICAgICAgICAiXG5WYWxvciBKdXN0bzoiLCBzcHJpbnRmKCIlLjJmIix2YWxvcl9qdXN0byksCiAgICAgICAgICAgICAgICAgICAgICAgICJcbkRpZmVyZW7Dp2E6Iiwgc3ByaW50ZigiJS4yZiIsZGlmZXJlbmNhKSksCiAgICAgICAgICAgIG5hbWU9IkpvZ2Fkb3JlcyIsCiAgICAgICAgICAgIHR5cGU9J3NjYXR0ZXInLCBtb2RlPSdtYXJrZXJzJykgJT4lCiAgYWRkX3NlZ21lbnRzKHggPSAwLCB4ZW5kID0gMWU4LCB5ID0gMCwgeWVuZCA9IDFlOCwgbmFtZT0iRXF1aWzDrWJyaW8iKQpkaXNwbGF5LmdyYXBoKGZpZykKYGBgCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgZnVuw6fDo28gYXV4aWxpYXIgcGFyYSBleGVyY2ljaW9zIDYuMiBlIDYuMwoKCmNyZWF0ZV9mYWlyX3ZhbHVlX21vZGVsID0gLiAlPiUgCiAgZmlsdGVyKGxlYWd1ZSAlaW4lIGJlc3RfbGVhZ3VlcykgJT4lCiAgc2VsZWN0KFBvc2l0aW9uLCBldXJfdmFsdWUsIGFsbF9vZihoYWJpbGlkYWRlcykpICU+JQogIChmdW5jdGlvbiAoeCkgeyB4W2NvbXBsZXRlLmNhc2VzKHgpLCBdIH0pICU+JQogIHJhbmRvbUZvcmVzdChldXJfdmFsdWV+LiwgZGF0YT0uKQoKY3JlYXRlX3ByZWRpY3Rpb25fZHQgPSBmdW5jdGlvbiAoZGF0YSwgcmYpIHsKICBkYXRhICU+JQogICAgZmlsdGVyKCFsZWFndWUgJWluJSBiZXN0X2xlYWd1ZXMpICU+JQogICAgbXV0YXRlKGZhaXJfdmFsdWUgPSBwcmVkaWN0KHJmLCAuKSwgZ2FwID0gZXVyX3ZhbHVlIC0gZmFpcl92YWx1ZSkgJT4lCiAgICBzZWxlY3QoSUQsIG5hbWUsIGNsdWIsIGxlYWd1ZSwgZXVyX3ZhbHVlLCBmYWlyX3ZhbHVlLCBnYXApCn0KCnByZWRpY3RfZmFpcl92YWx1ZSA9IGZ1bmN0aW9uIChkYXRhLCAuLi4pIHsKICBwbGF5ZXJzID0gZGF0YSAlPiUgZmlsdGVyKFBvc2l0aW9uICVpbiUgYyguLi4pKQogIHBsYXllcnMucmFuZG9tX2ZvcmVzdCA9IHBsYXllcnMgJT4lIGNyZWF0ZV9mYWlyX3ZhbHVlX21vZGVsCiAgcGxheWVycy5wcmVkaWN0aW9uID0gY3JlYXRlX3ByZWRpY3Rpb25fZHQocGxheWVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbGF5ZXJzLnJhbmRvbV9mb3Jlc3QpCiAgcGxheWVycy5wcmVkaWN0aW9uCn0KCnBsb3RfYWR2YW5jZWRfYW5hbHl0aWNzID0gZnVuY3Rpb24gKGRhdGEsIC4uLikgewogIHBsYXllcnMucHJlZGljdGlvbiA9IHByZWRpY3RfZmFpcl92YWx1ZShkYXRhLCAuLi4pCiAgcGxvdF9seSgpIC0+IGZpZwogIGZpZyA8LSBmaWcgJT4lCiAgYWRkX3RyYWNlKGRhdGEgPSBwbGF5ZXJzLnByZWRpY3Rpb24sIHg9IH5ldXJfdmFsdWUsIHk9IH5mYWlyX3ZhbHVlLAogICAgICAgICAgICB0ZXh0PX5wYXN0ZShuYW1lLCAiXG5WYWxvciBNZXJjOiIsIHNwcmludGYoIiUuMmYiLGV1cl92YWx1ZSksCiAgICAgICAgICAgICAgICAgICAgICAgICJcblZhbG9yIEp1c3RvOiIsIHNwcmludGYoIiUuMmYiLGZhaXJfdmFsdWUpLAogICAgICAgICAgICAgICAgICAgICAgICAiXG5EaWZlcmVuw6dhOiIsIHNwcmludGYoIiUuMmYiLGdhcCkpLAogICAgICAgICAgICBuYW1lPSJKb2dhZG9yZXMiLAogICAgICAgICAgICB0eXBlPSdzY2F0dGVyJywgbW9kZT0nbWFya2VycycpICU+JQogIGFkZF9zZWdtZW50cyh4ID0gMCwgeGVuZCA9IDFlOCwgeSA9IDAsIHllbmQgPSAxZTgsIG5hbWU9IkVxdWlsw61icmlvIikKICBkaXNwbGF5LmdyYXBoKGZpZykKfQpgYGAKIyMgNi4yLglGYcOnYSB1bSBtb2RlbG8gZGUgQWR2YW5jZWQgQW5hbHl0aWNzIHBhcmEgTWVpbyBDYW1waXN0YXMKCmBgYHtyfQpwbG90X2FkdmFuY2VkX2FuYWx5dGljcyhmaWZhLCAiT3V0c2lkZS1taWQiLCAiQ2VudGVyLW1pZCIpCmBgYAoKIyMgNi4zLglGYcOnYSB1bSBtb2RlbG8gZGUgQWR2YW5jZWQgQW5hbHl0aWNzIHBhcmEgWmFxdWVpcm9zIENlbnRyYWlzCgpgYGB7cn0KcGxvdF9hZHZhbmNlZF9hbmFseXRpY3MoZmlmYSwgIk91dHNpZGUtYmFjayIsICJDZW50ZXItYmFjayIpCmBgYAoKCgojIDcgLSBDb25jbHVzw6NvCgpFc3Bhw6dvIHBhcmEgYSBjb25jbHVzw6NvCgpgYGB7cn0KZiA9IHByZWRpY3RfZmFpcl92YWx1ZShmaWZhLCAiRm9yd2FyZCIpCm0gPSBwcmVkaWN0X2ZhaXJfdmFsdWUoZmlmYSwgIk91dHNpZGUtbWlkIiwgIkNlbnRlci1taWQiKQpiID0gcHJlZGljdF9mYWlyX3ZhbHVlKGZpZmEsICJPdXRzaWRlLWJhY2siLCAiQ2VudGVyLWJhY2siKQpmW29yZGVyKGYkZ2FwKSwgYygibmFtZSIsICJnYXAiLCAiY2x1YiIpXQptW29yZGVyKG0kZ2FwKSwgYygibmFtZSIsICJnYXAiLCAiY2x1YiIpXQpiW29yZGVyKGIkZ2FwKSwgYygibmFtZSIsICJnYXAiLCAiY2x1YiIpXQpgYGAKCkRpZ2EgcXVhaXMgZG9pcyAqKkF0YWNhbnRlcyoqIHBvZGVyaWFtIHNlciBhZHF1aXJpZG9zIHBlbG8gY2x1YmUgZSBjb21lbnRlIG8gbW90aXZvOiBaLiBJYnJhaGltb3ZpYywgSm9uYXMKCkRpZ2EgcXVhaXMgZG9pcyAqKk1laW8gQ2FtcGlzdGFzKiogcG9kZXJpYW0gc2VyIGFkcXVpcmlkb3MgcGVsbyBjbHViZSBlIGNvbWVudGUgbyBtb3Rpdm86CUEuIFdpdHNlbCwgQWRyaWVuIFNpbHZhCgoKRGlnYSBxdWFpcyBkb2lzICoqWmFxdWVpcm9zIENlbnRyYWlzKiogcG9kZXJpYW0gc2VyIGFkcXVpcmlkb3MgcGVsbyBjbHViZSBlIGNvbWVudGUgbyBtb3Rpdm86IFAuIE1lcnRlc2Fja2VyLCBELiBTcm5hCgo=